package org.gicentre.apps.hide;

import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.gicentre.apps.hide.TreemapState.AppearanceType;
import org.gicentre.apps.hide.TreemapState.Layout;
import org.gicentre.data.DataField;
import org.gicentre.data.summary.SummariseField;
import org.gicentre.data.summary.SummariseNull;
import org.gicentre.hive.Expression;
import org.gicentre.hive.ExpressionNotSupportedException;
import org.gicentre.hive.Hive;
import org.gicentre.hive.Type;
import org.gicentre.utils.gui.Tooltip;
import org.gicentre.utils.spatial.Direction;

// import com.sun.org.apache.bcel.internal.generic.DADD;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;

/**The GUI
 * Extends TreemapState, so also serves as this.
 * 
 * This does no event handling itself. To use it, a Processing sketch
 * must call draw() and mouseClicked() whenever these events are fired
 * 
 * @author Aidan Slingsby, giCentre
 *
 */
public class TreemapStateGui implements Hive{

	private TreemapState state;
	
	private int x,y;
	private PFont f;
	private PApplet applet;
	private int textHeight,plusMinusW,swapArrowW;
	Rectangle bounds;
	
	Tooltip clickHereToStartTooltip;
	
	protected Layout defaultLayout=Layout.ONE_DIM_STRIP;
	
	//Rectangles for all the clickable words, so mouse detection is easier
	//These are laid out by layout()
	private Rectangle[] hierarchyRectangles; //0 is the label... so 1 longer than the hierarchyFields 
	private Rectangle[][] sizeRectangles; 
	private Rectangle[][] orderRectangles; 
	private Rectangle[][] colourRectangles;
	private Rectangle[] layoutRectangles; 
	private Rectangle[] plusRectangles; 
	private Rectangle[] minusRectangles;
	private Rectangle[] appearanceRectangles;
	private Rectangle[] swapRectangles;
	private Rectangle[] filterRectangles;
	
	private AppearanceType[] appearanceTypesEditing=new AppearanceType[0];
	
	private boolean flagToRelayoutGui=true;//flag to relayout GUI
	
	private int guiHeight;
	
	private boolean showMenu=false;
	private Rectangle menuBounds=null;
	private List<String> menuItems=null;
	private int mouseXOnMenuOpen,mouseYOnMenuOpen;
	

	/**Constructor
	 * 
	 * @param applet Main processing sketch
	 * @param x
	 * @param y
	 * @param font
	 * @param numOrderStates Number of order dimensions (e.g. 1 for only 1D orders, 2 for 2D orders,...  - if supported by other classes)
	 * @param numSizeStates Number of size dimensions (e.g. 2 for 2D sizes - if supported by other classes)
	 * @param numColourStates Number of colour dimensions (e.g. 2 for lightness and saturation - if supported by other classes)
	 * @param allowedHierarchyFields All the data variables offered by the GUI for the hierarchy 
	 * @param allowedOrderFields All the summary variables offered by the GUI for orders
	 * @param allowedSizeFields All the summary variables offered by the GUI for sizes
	 * @param allowedColourFields All the summary variables offered by the GUI for colours
	 * @param allowedLayouts All the layouts that are offered in the GUI
	 */
	public TreemapStateGui(PApplet applet, int x,int y, PFont font,int numOrderStates, int numSizeStates, int numColourStates,List<DataField> allowedHierarchyFields,List<SummariseField> allowedOrderFields,List<SummariseField> allowedSizeFields,List<SummariseField> allowedColourFields, List<Layout> allowedLayouts) {
		state=new TreemapState(numOrderStates,numSizeStates,numColourStates,allowedHierarchyFields,allowedOrderFields,allowedSizeFields,allowedColourFields,allowedLayouts);
		this.x=x;
		this.y=y;
		this.f=font;
		this.applet=applet;
		
		
		//mkae sure there are no duplicates
		//If empty, add a null value...
		//...but the hierarchy field values need at least one value 

		//set allowed hierarchy fields
		state.allowedHierarchyFields=new ArrayList<DataField>();
		if (allowedHierarchyFields!=null){
			Iterator<DataField> it = allowedHierarchyFields.iterator();
			while (it.hasNext()){
				DataField dataField=it.next();
				if (dataField!=null && !state.allowedHierarchyFields.contains(dataField)){
					state.allowedHierarchyFields.add(dataField);
				}
			}
		}
		if (state.allowedHierarchyFields.isEmpty()){			
			System.err.println("There must be at least one hierarchy field allowed. Nulls are not allowed");
			return;
		}
		
		//set allowed size fields
		state.allowedSizeFields=new ArrayList<SummariseField>();
		if (allowedSizeFields!=null){
			Iterator<SummariseField> it = allowedSizeFields.iterator();
			while (it.hasNext()){
				SummariseField dataField=it.next();
				if (!state.allowedSizeFields.contains(dataField)) state.allowedSizeFields.add(dataField);
			}
		}
		if (state.allowedSizeFields.isEmpty()) state.allowedSizeFields.add(null);//make sure there's at least one element, even if it's null
		
		//set allowed order fields		
		state.allowedOrderFields=new ArrayList<SummariseField>();
		if (allowedOrderFields!=null){
			Iterator<SummariseField> it = allowedOrderFields.iterator();
			while (it.hasNext()){
				SummariseField dataField=it.next();
				if (!state.allowedOrderFields.contains(dataField)){
					state.allowedOrderFields.add(dataField);
				}
			}
		}
		if (state.allowedOrderFields.isEmpty()) state.allowedOrderFields.add(null);//make sure there's at least one element, even if it's null
		
		//set allowed colour fields
		state.allowedColourFields=new ArrayList<SummariseField>();
		if (allowedColourFields!=null){
			state.allowedColourFields=allowedColourFields;
		}
		if (state.allowedColourFields.isEmpty()) state.allowedColourFields.add(null);//make sure there's at least one element, even if it's null

		//set allowed layouts
		state.allowedLayouts=new ArrayList<Layout>();
		if (allowedLayouts!=null){
			state.allowedLayouts=allowedLayouts;
		}
		
		applet.textFont(f);
		textHeight=(int)(applet.textAscent()+applet.textDescent());
		plusMinusW=(int)applet.textWidth("+");
		swapArrowW=(int)applet.textWidth("\u25C7");
		//calculate bounds
		int w =(int)applet.textWidth("Hierarchy:");//the widest label
		
		//work out gui height
		
		guiHeight=(textHeight)*(2+state.sizeFields.length+state.orderFields.length+state.colourFields.length+1+1+1)+4+4;
		
		bounds=new Rectangle(x,y,w+plusMinusW,guiHeight);
		
		flagToRelayoutGui=true;
		
		clickHereToStartTooltip=new Tooltip(applet,f,12,150);
		clickHereToStartTooltip.setAnchor(Direction.NORTH_WEST);
		clickHereToStartTooltip.setPointerSize(20);
		clickHereToStartTooltip.showPointer(true);
		clickHereToStartTooltip.setText("Click '+' to start adding data");

	}

	
	/** Sets the position for the top left corner
	 * 
	 * @param x
	 * @param y
	 */
	public void setXY(int x,int y){
		this.x=x;
		this.y=y;
		flagToRelayoutGui=true;
	}
	
	/**Lays out whether each bit of text goes, for the current state. Called when the state changes.
	 * Lays text out so that the correct number of columns
	 * are used and they are the correct widths to fit the text in,
	 */
	private void layoutRectangles(){
		hierarchyRectangles=new Rectangle[state.hierarchyFields.length+1];
		sizeRectangles=new Rectangle[state.sizeFields.length][state.hierarchyFields.length+1];
		orderRectangles=new Rectangle[state.orderFields.length][state.hierarchyFields.length+1];
		colourRectangles=new Rectangle[state.colourFields.length][state.hierarchyFields.length+1];
		layoutRectangles=new Rectangle[state.hierarchyFields.length+1];
		appearanceRectangles=new Rectangle[state.hierarchyFields.length+1];
		filterRectangles=new Rectangle[state.hierarchyFields.length+1];
		
		plusRectangles=new Rectangle[state.hierarchyFields.length+1];
		minusRectangles=new Rectangle[state.hierarchyFields.length+1];

		swapRectangles=new Rectangle[state.hierarchyFields.length+1];

		//check that an appearanceTypeEditing value for all levels
		AppearanceType[] oldAppearanceTypesEditing=appearanceTypesEditing;
		appearanceTypesEditing=new AppearanceType[getNumLevels()];

		//get last non-null attrib from old
		AppearanceType defaultAppearanceType=AppearanceType.values()[0];
		for (int i=0;i<oldAppearanceTypesEditing.length;i++){
			if (oldAppearanceTypesEditing[i]!=null)
				defaultAppearanceType=oldAppearanceTypesEditing[i];
		}

		for (int i=0;i<getNumLevels();i++){
			if (i<oldAppearanceTypesEditing.length && oldAppearanceTypesEditing[i]!=null)
				appearanceTypesEditing[i]=oldAppearanceTypesEditing[i];
			else
				appearanceTypesEditing[i]=defaultAppearanceType;
		}


		//Find maximum lengths for each column
		int[] colWidths=new int[state.hierarchyFields.length+1];
		applet.textFont(f);
		//do labels
		colWidths[0]=(int)applet.textWidth("Appearance:");//the widest label
		//then do the others
		for (int col=0;col<state.hierarchyFields.length;col++){
			colWidths[col+1]=0;
			int w = (int)applet.textWidth(state.hierarchyFields[col].getName());
			if (w>colWidths[col+1]){
				colWidths[col+1]=w;
			}
			for (int j=0;j<state.sizeFields.length;j++){
				SummariseField summariseField=state.sizeFields[j][col];
				if (summariseField==null || summariseField instanceof SummariseNull){
					w = (int)applet.textWidth("FIXED");
				}
				else{
					w = (int)applet.textWidth(summariseField.getName());
				}
				if (w>colWidths[col+1]){
					colWidths[col+1]=w;
				}
			}
			for (int j=0;j<state.orderFields.length;j++){
				SummariseField summariseField=state.orderFields[j][col];
				if (summariseField==null || summariseField instanceof SummariseNull){
					w = (int)applet.textWidth("HIERARCHY");
				}
				else{
					w = (int)applet.textWidth(summariseField.getName());
				}				
				if (w>colWidths[col+1]){
					colWidths[col+1]=w;
				}
			}
			for (int j=0;j<state.colourFields.length;j++){
				SummariseField summariseField=state.colourFields[j][col];
				if (summariseField==null){
					w = (int)applet.textWidth("<no value>");
				}
				else if (summariseField instanceof SummariseNull){
					w = (int)applet.textWidth("HIERARCHY");
				}
				else{
					w = (int)applet.textWidth(summariseField.getName());
				}
				if (w>colWidths[col+1]){
					colWidths[col+1]=w;
				}
			}
			w = (int)applet.textWidth(state.layouts[col].toString());
			if (w>colWidths[col+1]){
				colWidths[col+1]=w;
			}
			w = (int)applet.textWidth(appearanceTypesEditing[col].toString()+textHeight+2);
			if (w>colWidths[col+1]){
				colWidths[col+1]=w;
			}
			if (state.filterValues[col]!=null){ //this is the case for <all> (no filter at this level)
				w = (int)(applet.textWidth(state.filterValues[col].toString())+applet.textWidth(" x"));
			}
			else{
				w = (int)(applet.textWidth("<all>"));
			}
			if (w>colWidths[col+1]){
				colWidths[col+1]=w;
			}

		}
		//create rectangles
		int rectHeight=textHeight+1;
		int cumX=5;
		for (int col=0;col<state.hierarchyFields.length+1;col++){
			hierarchyRectangles[col]=new Rectangle(cumX,textHeight,colWidths[col],rectHeight);
			for (int j=0;j<state.sizeFields.length;j++){
				sizeRectangles[j][col]=new Rectangle(cumX,(textHeight)*(2+j),colWidths[col],rectHeight);
			}
			for (int j=0;j<state.orderFields.length;j++){
				orderRectangles[j][col]=new Rectangle(cumX,(textHeight)*(2+state.sizeFields.length+j),colWidths[col],rectHeight);
			}
			for (int j=0;j<state.colourFields.length;j++){
				colourRectangles[j][col]=new Rectangle(cumX,(textHeight)*(2+state.sizeFields.length+state.orderFields.length+j),colWidths[col],rectHeight);
			}
			layoutRectangles[col]=new Rectangle(cumX,(textHeight)*(2+state.sizeFields.length+state.orderFields.length+state.colourFields.length),colWidths[col],rectHeight);

			minusRectangles[col]=new Rectangle(cumX+colWidths[col]/2-plusMinusW/2,0,plusMinusW,rectHeight);
			plusRectangles[col]=new Rectangle(cumX+colWidths[col],0,plusMinusW,rectHeight);
			if (col<state.hierarchyFields.length){
				swapRectangles[col]=new Rectangle(cumX+colWidths[col]+1,textHeight,swapArrowW,rectHeight);
			}


			appearanceRectangles[col]=new Rectangle(cumX,(textHeight)*(2+state.sizeFields.length+state.orderFields.length+state.colourFields.length+1)+4,colWidths[col],rectHeight);			
			
			filterRectangles[col]=new Rectangle(cumX,(textHeight)*(2+state.sizeFields.length+state.orderFields.length+state.colourFields.length+1+1)+4,colWidths[col],rectHeight);			
			
			cumX+=colWidths[col]+10;
		}
		bounds = new Rectangle(x,y,cumX+plusMinusW,guiHeight);
	}
		
	
	
	 /** Restores state from hive expression
	  * 
	  * @param hive
	  * @return Success or otherwise
	  */
	 public boolean restoreStateFromHiveString(String hive){
		 	this.state.restoreStateFromHiveString(hive);
		 	flagToRelayoutGui=true;
			return true;
		}

	
	/**Draws GUI
	 * 
	 */
	public void draw(){
		if (flagToRelayoutGui){
			flagToRelayoutGui=false;
			layoutRectangles();
		}
		int mouseX=applet.mouseX-x;
		int mouseY=applet.mouseY-y;

		applet.noStroke();
		applet.fill(240,220);
		applet.rect(this.bounds.x,this.bounds.y,(int)hierarchyRectangles[0].getMaxX(),this.bounds.height);
		applet.fill(255,220);
		applet.rect(this.bounds.x+(int)hierarchyRectangles[0].getMaxX(),this.bounds.y,this.bounds.width-(int)hierarchyRectangles[0].getMaxX(),this.bounds.height);
		
		
		applet.textFont(f);
		applet.fill(40);
		
		for (int col=0;col<state.hierarchyFields.length+1;col++){
			if (col==0){
				applet.textAlign(PApplet.RIGHT,PApplet.TOP);
				applet.text("Hierarchy:",x+(int)hierarchyRectangles[col].getMaxX(),y+hierarchyRectangles[col].y);
				for (int j=0;j<state.sizeFields.length;j++){
					String label="Size";
					if (state.sizeFields.length>1){
						label+=(j+1);
					}
					applet.text(label+":",x+(int)sizeRectangles[j][col].getMaxX(),y+sizeRectangles[j][col].y);
				}
				for (int j=0;j<state.orderFields.length;j++){
					String label="Order";
					if (state.orderFields.length>1){
						label+=(j+1);
					}
					applet.text(label+":",x+(int)orderRectangles[j][col].getMaxX(),y+orderRectangles[j][col].y);
				}
				for (int j=0;j<state.colourFields.length;j++){
					String label="Colour";
					if (state.colourFields.length>1){
						label+=(j+1);
					}
					applet.text(label+":",x+(int)colourRectangles[j][col].getMaxX(),y+colourRectangles[j][col].y);
				}
				applet.text("Layout:",x+(int)layoutRectangles[col].getMaxX(),y+layoutRectangles[col].y);
				applet.text("Appearance:",x+(int)appearanceRectangles[col].getMaxX(),y+appearanceRectangles[col].y);
				applet.text("Filter:",x+(int)filterRectangles[col].getMaxX(),y+filterRectangles[col].y);

				//and do the first plus sign
				applet.textAlign(PApplet.CENTER,PApplet.TOP);
				Rectangle r=plusRectangles[0];
				if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(40);
				}
				applet.text("+",x+(int)r.getCenterX(),y+r.y);
				if (state.hierarchyFields.length==0){
					clickHereToStartTooltip.draw((float)r.getMaxX(),(float)r.getMaxY());
				}
			}
			else{
				applet.textAlign(PApplet.LEFT,PApplet.TOP);

				//minus signs
				Rectangle r=minusRectangles[col];
				if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(40);
				}
				applet.text("\u2013",x+(int)r.getX(),y+r.y);

				//plus signs
				r=plusRectangles[col];
				if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(40);
				}
				applet.text("+",x+(int)r.getX(),y+r.y);

				//swap arrows
				r=swapRectangles[col];
				if (r!=null){
					if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(40);
					}
					applet.text("\u25C7",x+(int)r.getX(),y+r.y);
				}

				
				r = hierarchyRectangles[col];
				if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(80);
				}
				applet.text(state.hierarchyFields[col-1].getName(),x+(int)r.getX(),y+r.y);

				for (int j=0;j<state.sizeFields.length;j++){
					r = sizeRectangles[j][col];
					if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(80);
					}
					SummariseField summariseField=state.sizeFields[j][col-1];
					String label=null;
					if (summariseField instanceof SummariseNull){
						label="FIXED";
					}
					else{
						label=summariseField.getName();
					}
					applet.text(label,x+(int)r.getX(),y+r.y);
				}

				for (int j=0;j<state.orderFields.length;j++){
					r = orderRectangles[j][col];
					if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
						applet.fill(150,0,0);
					}
					else if (!state.layouts[col-1].equals(Layout.TWO_DIMENSIONAL) && !state.layouts[col-1].equals(Layout.ABS_POSITION) && j>0){
						applet.fill(180);//faint if one one layout used
					}
					else{
						applet.fill(80);
					}
					SummariseField summariseField=state.orderFields[j][col-1];
					String label;
					if (summariseField instanceof SummariseNull==false){
						label=summariseField.getName();
					}
					else {
						label="NATURAL";
					}
					applet.text(label,x+(int)r.getX(),y+r.y);
				}

				for (int j=0;j<state.colourFields.length;j++){
					r = colourRectangles[j][col];
					if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(80);
					}
					SummariseField summariseField=state.colourFields[j][col-1];
					String label;
					if (summariseField==null){
						label="<none>";
					}
					else if (summariseField instanceof SummariseNull){
						label="HIERARCHY";
					}
					else{
						label=summariseField.getName();
					}
					applet.text(label,x+(int)r.getX(),y+r.y);
				}

				r = layoutRectangles[col];
				if ((!showMenu && r.contains(mouseX,mouseY)) || (showMenu && r.contains(mouseXOnMenuOpen,mouseYOnMenuOpen))){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(80);
				}
				applet.text(state.layouts[col-1].toString(),x+(int)r.getX(),y+r.y);
				
				r = appearanceRectangles[col];
				if (r.contains(mouseX,mouseY) && mouseX<r.getMaxX()-textHeight){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(80);
				}
				applet.text(appearanceTypesEditing[col-1].toString(),x+(int)r.getX(),y+r.y);

				if (appearanceTypesEditing[col-1].checkBox()){
					applet.pushStyle();
					applet.strokeWeight(1.8f);
					if (r.contains(mouseX,mouseY) && mouseX>r.getMaxX()-textHeight-2){
						applet.stroke(150,0,0);
					}
					else{
						applet.stroke(80);
					}
					if (state.appearanceValues[col-1].get(appearanceTypesEditing[col-1])==0){
						applet.noFill();
					}
					else{
						applet.fill(150);
					}
					applet.rect((int)(x+r.getMaxX()-textHeight+2),(int)(y+r.getCenterY()-1-textHeight/3f),textHeight*(2/3f),textHeight*(2/3f));
					applet.popStyle();
				}
				else{
					//draw up triangle
					if (r.contains(mouseX,mouseY) && mouseX>r.getMaxX()-textHeight && mouseY<r.getCenterY()){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(80);
					}
					applet.triangle(
							(int)(x+r.getMaxX()-textHeight+2),(int)(y+r.getCenterY()-2),
							(int)(x+r.getMaxX()-(textHeight/2)),(int)(y+r.y),
							(int)(x+r.getMaxX()),(int)(y+r.getCenterY()-2)
					);

					//draw down triangle
					if (r.contains(mouseX,mouseY) && mouseX>r.getMaxX()-textHeight && mouseY>r.getCenterY()){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(80);
					}
					applet.triangle(
							(int)(x+r.getMaxX()-textHeight+2),(int)(y+r.getCenterY()+2),
							(int)(x+r.getMaxX()-(textHeight/2)),(int)(y+r.getMaxY()),
							(int)(x+r.getMaxX()),(int)(y+r.getCenterY()+2)
					);
				}				
				
				r = filterRectangles[col];
				if (r.contains(mouseX,mouseY) && mouseX<r.getMaxX()-20){
					applet.fill(150,0,0);
				}
				else{
					applet.fill(80);
				}
				Object value = state.filterValues[col-1];
				if (value==null){
					value="<all>";
				}
				applet.text(value.toString(),x+(int)r.getX(),y+r.y);
				if (state.filterValues[col-1]!=null){
					if (r.contains(mouseX,mouseY) && mouseX>r.getMaxX()-10){
						applet.fill(150,0,0);
					}
					else{
						applet.fill(80);
					}
					applet.textAlign(PConstants.RIGHT, PConstants.TOP);
					applet.text("x",x+(int)r.getMaxX(),y+r.y);					
				}
			}
			
			
			//draw menu box
			if (showMenu){
				applet.fill(250);
				applet.stroke(150);
				applet.strokeWeight(1);
				applet.rect(menuBounds.x,menuBounds.y,menuBounds.width,menuBounds.height);
				applet.noStroke();
				applet.textSize(12);
				applet.textAlign(PConstants.LEFT,PConstants.TOP);
				int y=menuBounds.y+4;
				for (String label:menuItems){
					if (applet.mouseX>menuBounds.x 
							&& applet.mouseX<menuBounds.x+menuBounds.width 
							&& applet.mouseY>y
							&& applet.mouseY<y+12){
						applet.fill(230);
						applet.rect(menuBounds.x,y,menuBounds.width,12);
					}
					applet.fill(80);
					applet.text(label,menuBounds.x+5,y);
					y+=12;
				}
			}
		}
		
	}
	
	/**Return the bounds
	 * 
	 * @return
	 */
	public Rectangle getBounds(){
		return bounds;
	}
	

	/**Find whether a coordinate is inside the GUI area
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public boolean contains(float x, float y){
		return bounds.contains(x,y);
	}
	

	/**Should call this when the mouse is clicked
	 * 
	 */
	public void mouseClicked() {
		
		menuBounds=null;

		//offset mouse coordinate with GUI position - take mouse coords from the main Processing sketch
		int mouseX=applet.mouseX-x;
		int mouseY=applet.mouseY-y;

		//Only do something if mouse is in area
		if (bounds.contains(applet.mouseX,applet.mouseY)){

			//Change the value of any hierarchy variables clicked (forward/back with left/right mouse button) 
			for (int i=1;i<state.hierarchyFields.length+1;i++){
				if (hierarchyRectangles[i].contains(mouseX,mouseY)){
					int idx=state.allowedHierarchyFields.indexOf(state.hierarchyFields[i-1]);
					if (idx==-1){
						idx=0;
					}
					if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
						idx++;
						if (idx>state.allowedHierarchyFields.size()-1){
							idx=0;
						}
					}
					else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
						idx--;
						if (idx<0){
							idx=state.allowedHierarchyFields.size()-1;
						}
						List<String> values=new ArrayList<String>();
						for (DataField dataField:state.allowedHierarchyFields)
							values.add(dataField.getName());
						layoutMenu(values, mouseX, mouseY);
					}
					state.hierarchyFields[i-1]=state.allowedHierarchyFields.get(idx);
					
					//check filter value is valid for this variable - otherwise set to null (<all>)
					if (state.filterValues[i-1]!=null
							&& !state.hierarchyFields[i-1].getOrderValues().contains(state.filterValues[i-1])){
						state.filterValues[i-1]=null;
					}
					
					state.hierHasChanged=true;
					flagToRelayoutGui=true;
				}
			}

			//Change the value of any order variables clicked (forward/back with left/right mouse button) 
			for (int j=0;j<state.orderFields.length;j++){
				for (int i=1;i<state.hierarchyFields.length+1;i++){ //use hierarchyFields.length, because it's per level
					if (orderRectangles[j][i].contains(mouseX,mouseY)){
						int idx=state.allowedOrderFields.indexOf(state.orderFields[j][i-1]);
						if (idx==-1){
							idx=0;
						}
						if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
							idx++;
							if (idx>state.allowedOrderFields.size()-1){
								idx=0;
							}
						}
						else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
							idx--;
							if (idx<0){
								idx=state.allowedOrderFields.size()-1;
							}
						}
						state.orderFields[j][i-1]=state.allowedOrderFields.get(idx);
						state.orderHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}

			//Change the value of any size variables clicked (forward/back with left/right mouse button) 
			for (int j=0;j<state.sizeFields.length;j++){
				for (int i=1;i<state.hierarchyFields.length+1;i++){ //use hierarchyFields.length, because it's per level
					if (sizeRectangles[j][i].contains(mouseX,mouseY)){
						int idx=state.allowedSizeFields.indexOf(state.sizeFields[j][i-1]);
						if (idx==-1){
							idx=0;
						}
						if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
							idx++;
							if (idx>state.allowedSizeFields.size()-1){
								idx=0;
							}
						}
						else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
							idx--;
							if (idx<0){
								idx=state.allowedSizeFields.size()-1;
							}
						}
						state.sizeFields[j][i-1]=state.allowedSizeFields.get(idx);
						state.sizeHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}

			//Change the value of any colour variables clicked (forward/back with left/right mouse button) 
			for (int j=0;j<state.colourFields.length;j++){
				for (int i=1;i<state.hierarchyFields.length+1;i++){ //use hierarchyFields.length, because it's per level
					if (colourRectangles[j][i].contains(mouseX,mouseY)){
						int idx=state.allowedColourFields.indexOf(state.colourFields[j][i-1]);
						if (idx==-1){
							idx=0;
						}
						if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
							idx++;
							if (idx>state.allowedColourFields.size()-1){
								idx=0;
							}
						}
						else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
							idx--;
							if (idx<0){
								idx=state.allowedColourFields.size()-1;
							}
						}
						state.colourFields[j][i-1]=state.allowedColourFields.get(idx);
						state.colourHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}
			
			//Change the value of any layouts clicked (forward/back with left/right mouse button) 
			for (int i=1;i<state.hierarchyFields.length+1;i++){
				if (layoutRectangles[i].contains(mouseX,mouseY)){
					int idx=state.allowedLayouts.indexOf(state.layouts[i-1]);
					if (idx==-1){
						idx=0;
					}
					if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
						idx++;
						if (idx>state.allowedLayouts.size()-1){
							idx=0;
						}
					}
					else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
						idx--;
						if (idx<0){
							idx=state.allowedLayouts.size()-1;
						}
					}
					state.layouts[i-1]=state.allowedLayouts.get(idx);
					state.layoutHasChanged=true;
					flagToRelayoutGui=true;
				}
			}

			//Change any appearance type clicked (forward/back with left/right mouse button)
			//change the actual value if the arrows are clicked
			for (int i=1;i<state.appearanceValues.length+1;i++){
				//change appearance type being edited
				if (appearanceRectangles[i].contains(mouseX,mouseY) && mouseX<appearanceRectangles[i].getMaxX()-textHeight){
					AppearanceType[] appearanceValues=AppearanceType.values();
					int idx=appearanceTypesEditing[i-1].ordinal();
					if (idx==-1){
						idx=0;
					}
					if (applet.mouseEvent.getButton()==MouseEvent.BUTTON1){
						idx++;
						if (idx>=appearanceValues.length){
							idx=0;
						}
					}
					else if (applet.mouseEvent.getButton()==MouseEvent.BUTTON3){
						idx--;
						if (idx<0){
							idx=appearanceValues.length-1;
						}
					}
					appearanceTypesEditing[i-1]=appearanceValues[idx];
					flagToRelayoutGui=true;
				}
				if (appearanceRectangles[i].contains(mouseX,mouseY)){
					AppearanceType appearanceType=appearanceTypesEditing[i-1];
					//if a checkbox
					if (appearanceType.checkBox()){
						 if (mouseX>appearanceRectangles[i].getMaxX()-textHeight){
							 if (state.appearanceValues[i-1].get(appearanceType)==0d){
								 state.appearanceValues[i-1].put(appearanceType,1d);
							 }
							 else{
								 state.appearanceValues[i-1].put(appearanceType,0d);
							 }
							 //flag to build if user allows/disallows nulls
							 if (appearanceType==AppearanceType.INCLUDE_NULLS){
								 state.hierHasChanged=true;
							 }
							 //show labels if user changes autosize text
//							 if (appearanceType==AppearanceType.AUTOSIZE_LABELS){
//								 state.appearanceValues[i-1].put(AppearanceType.SHOW_LABELS,1d);
//							 }
							 state.appearanceHasChanged=true;
						 }
					}
					else{
						//if clicked on up button
						if (appearanceRectangles[i].contains(mouseX,mouseY) && mouseX>appearanceRectangles[i].getMaxX()-textHeight && mouseY<appearanceRectangles[i].getCenterY()){

							double v=state.appearanceValues[i-1].get(appearanceType);
							v+=appearanceType.incrementSize();
							if (v>appearanceType.maxValue()){
								v=appearanceType.maxValue();
							}
							state.appearanceValues[i-1].put(appearanceType,v);							
							//show labels if text size/opacity is changed
							if (appearanceType==AppearanceType.LABEL_OPACITY){
								state.appearanceValues[i-1].put(AppearanceType.SHOW_LABELS,1d);
							}
							if (appearanceType==AppearanceType.LABEL_SIZE){
								state.appearanceValues[i-1].put(AppearanceType.SHOW_LABELS,1d);
//								state.appearanceValues[i-1].put(AppearanceType.AUTOSIZE_LABELS,0d);
							}
							//rebuild if padding is changed
							if (appearanceType==AppearanceType.PADDING){
								state.hierHasChanged=true;
							}
							//rebuild if border width is changed (unfortunately, this is only to force a treemap redraw rather then just the labels
							if (appearanceType==AppearanceType.BORDER_WIDTH){
								state.hierHasChanged=true;
							}
							if (appearanceType==AppearanceType.SIZE_DIFF){
								state.sizeHasChanged=true;
							}
							if (appearanceType==AppearanceType.ORDER_RANGE_REF){
								state.orderHasChanged=true;
							}
//							if (appearanceType==AppearanceType.COLOUR_RANGE_REF){
//								state.colourHasChanged=true;
//							}
							state.appearanceHasChanged=true;
}
						//if clicked on down button
						if (appearanceRectangles[i].contains(mouseX,mouseY) && mouseX>appearanceRectangles[i].getMaxX()-textHeight && mouseY>appearanceRectangles[i].getCenterY()){
							if (appearanceTypesEditing[i-1].equals(appearanceType)){
								double v=state.appearanceValues[i-1].get(appearanceType);
								v-=appearanceType.incrementSize();
								if (v<appearanceType.minValue()){
									v=appearanceType.minValue();
								}
								state.appearanceValues[i-1].put(appearanceType,v);
								//force a rebuild if user wants to include/exclude nulls
								//show labels if text size/opacity is changed
								if (appearanceType==AppearanceType.LABEL_OPACITY){
									state.appearanceValues[i-1].put(AppearanceType.SHOW_LABELS,1d);
								}
								if (appearanceType==AppearanceType.LABEL_SIZE){
									state.appearanceValues[i-1].put(AppearanceType.SHOW_LABELS,1d);
//									state.appearanceValues[i-1].put(AppearanceType.AUTOSIZE_LABELS,0d);
								}
								//rebuild if padding is changed
								if (appearanceType==AppearanceType.PADDING){
									state.hierHasChanged=true;
								}
								//rebuild if border width is changed (unfortunately, this is only to force a treemap redraw rather then just the labels
								if (appearanceType==AppearanceType.BORDER_WIDTH){
									state.hierHasChanged=true;
								}
								if (appearanceType==AppearanceType.SIZE_DIFF){
									state.sizeHasChanged=true;
								}
								if (appearanceType==AppearanceType.ORDER_RANGE_REF){
									state.orderHasChanged=true;
								}
//								if (appearanceType==AppearanceType.COLOUR_RANGE_REF){
//									state.colourHasChanged=true;
//								}
							}
							state.appearanceHasChanged=true;
						}
					}
				}
			}
			
			//Change the value of any filters clicked (forward/back with left/right mouse button) 
			for (int i=1;i<state.hierarchyFields.length+1;i++){
				if (filterRectangles[i].contains(mouseX,mouseY)){
					
					if (mouseX<filterRectangles[i].getMaxX()-15){
						//clicked on the word, not the x

						Integer idx=state.hierarchyFields[i-1].getOrderValues().indexOf(state.filterValues[i-1]);

						if ( (applet.mouseEvent.getModifiers() & InputEvent.BUTTON1_MASK ) !=0){
							if (idx==-1){
								idx=0;
							}
							else{
								idx++;
							}
							if (idx>state.hierarchyFields[i-1].getOrderValues().size()-1){
								idx=null;
							}
						}
						else if ( (applet.mouseEvent.getModifiers() & InputEvent.BUTTON3_MASK ) !=0){
							if (idx==-1){
								idx=state.hierarchyFields[i-1].getOrderValues().size()-1;
							}
							else{
								idx--;
							}
							if (idx<0){
								idx=null;
							}
						}
						if (idx==null){
							state.filterValues[i-1]=null;
						}
						else{
							state.filterValues[i-1]=state.hierarchyFields[i-1].getOrderValues().get(idx);
						}
					}
					else{
						//clicked on the x
						state.filterValues[i-1]=null;
					}
					state.hierHasChanged=true;
					flagToRelayoutGui=true;
				}
			}

			
			//minus signs
			if (minusRectangles.length>0 && state.allowedSizeFields!=null){
				for (int i=1;i<minusRectangles.length;i++){
					if (minusRectangles[i].contains(mouseX,mouseY)){
						cut(i-1);
						state.hierHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}

			//plus sign
			if (plusRectangles.length>0 && state.allowedSizeFields!=null){
				for (int i=0;i<plusRectangles.length;i++){//start at 0 for the plus sign
					if (plusRectangles[i].contains(mouseX,mouseY)){
						DataField defaultHierField = null;
						SummariseField[] defaultOrderField = new SummariseField[state.orderFields.length];
						SummariseField[] defaultSizeField = new SummariseField[state.sizeFields.length];
						SummariseField[] defaultColourField = new SummariseField[state.colourFields.length];
						Layout defaultLayout=null;
						//if the first, use the top of the allowed lists
						if (i==0){
							if (!state.allowedHierarchyFields.isEmpty()){
								defaultHierField=state.allowedHierarchyFields.get(0);
							}
							if (!state.allowedOrderFields.isEmpty()){
								for (int j=0;j<state.orderFields.length;j++){
									defaultOrderField[j]=state.allowedOrderFields.get(0);
								}
							}
							if (!state.allowedSizeFields.isEmpty()){
								for (int j=0;j<state.sizeFields.length;j++){
									defaultSizeField[j]=state.allowedSizeFields.get(0);
								}
							}
							if (!state.allowedColourFields.isEmpty()){
								for (int j=0;j<state.colourFields.length;j++){
									defaultColourField[j]=state.allowedColourFields.get(0);
								}
							}
							if (!state.allowedLayouts.isEmpty()){
								defaultLayout=state.allowedLayouts.get(0);
							}
						}
						else{
							//use the value to the left
							defaultHierField=state.hierarchyFields[i-1];
							for (int j=0;j<state.orderFields.length;j++){
								defaultOrderField[j]=state.orderFields[j][i-1];
							}
							for (int j=0;j<state.sizeFields.length;j++){
								defaultSizeField[j]=state.sizeFields[j][i-1];
							}
							for (int j=0;j<state.colourFields.length;j++){
								defaultColourField[j]=state.colourFields[j][i-1];
							}
							defaultLayout=state.layouts[i-1];
						}
						insert(i,defaultHierField,defaultOrderField,defaultSizeField,defaultColourField,defaultLayout);
						state.hierHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}

			//swap signs
			if (swapRectangles.length>0 && state.allowedSizeFields!=null){
				for (int i=0;i<swapRectangles.length-1;i++){
					if (swapRectangles[i].contains(mouseX,mouseY)){
						state.swap(i,i+1);
						state.hierHasChanged=true;
						flagToRelayoutGui=true;
					}
				}
			}
			
		}
		this.state.suppressHistoryAdd=false;
		applet.loop();
	}

	private void layoutMenu(List<String> menuItems,int x, int y){
		applet.textFont(f);
		//work out max width
		int maxW=0;
		for (String menuItem:menuItems){
			int w=(int)applet.textWidth(menuItem);
			if (w>maxW)
				maxW=w;
		}
		menuBounds=new Rectangle(x,y,maxW+8,menuItems.size()*f.getFont().getSize()+8);
		this.menuItems=menuItems;
	}
	
	public void mouseDragged(){
		if (!showMenu){
			List<String> values=new ArrayList<String>();

			for (int i=1;i<state.hierarchyFields.length+1;i++)
				if (hierarchyRectangles[i].contains(applet.mouseX,applet.mouseY))
						for (DataField field:state.allowedHierarchyFields)
							values.add(field.getName());

			for (int j=0;j<state.orderFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (orderRectangles[j][i].contains(applet.mouseX,applet.mouseY))
						for (SummariseField field:state.allowedOrderFields)
							if (field==null || field instanceof SummariseNull)
								values.add("NATURAL");
						    else
								values.add(field.getName());

			for (int j=0;j<state.sizeFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (sizeRectangles[j][i].contains(applet.mouseX,applet.mouseY))
						for (SummariseField field:state.allowedSizeFields)
							if (field==null || field instanceof SummariseNull)
								values.add("FIXED");
						    else
								values.add(field.getName());

			for (int j=0;j<state.colourFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (colourRectangles[j][i].contains(applet.mouseX,applet.mouseY))
						for (SummariseField field:state.allowedColourFields)
							if (field==null) 
								values.add("<none>");
							else if (field instanceof SummariseNull)
								values.add("HIERARCHY");
						    else
								values.add(field.getName());

			for (int i=1;i<state.layouts.length+1;i++)
				if (layoutRectangles[i].contains(applet.mouseX,applet.mouseY))
						for (Layout layout:state.allowedLayouts)
							values.add(layout.toString());

			for (int i=1;i<state.appearanceValues.length+1;i++)
				if (appearanceRectangles[i].contains(applet.mouseX,applet.mouseY))
					for (AppearanceType appearanceType:AppearanceType.values())
						values.add(appearanceType.toString());

			for (int i=1;i<state.filterValues.length+1;i++)
				if (filterRectangles[i].contains(applet.mouseX,applet.mouseY)){
					values.add("<all>");
					for (Object object:state.hierarchyFields[i-1].getOrderValues())
						values.add(object.toString());
					}

								
			if (!values.isEmpty()){		
				layoutMenu(values, applet.mouseX, applet.mouseY);
				mouseXOnMenuOpen=applet.mouseX;
				mouseYOnMenuOpen=applet.mouseY;
				showMenu=true;
			}
		}
		
	}
	
	public void mouseReleased(){
		if (showMenu && menuBounds.contains(applet.mouseX,applet.mouseY)){
			//if mouse is in an open menu, find the value then update
			int index=(applet.mouseY-4-menuBounds.y)/12;
			
			//check if it's any of the hierarchy controls
			for (int i=1;i<state.hierarchyFields.length+1;i++)
				if (hierarchyRectangles[i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
					state.hierarchyFields[i-1]=state.allowedHierarchyFields.get(index);
					//check filter value is valid for this variable - otherwise set to null (<all>)
					if (state.filterValues[i-1]!=null
							&& !state.hierarchyFields[i-1].getOrderValues().contains(state.filterValues[i-1])){
						state.filterValues[i-1]=null;
					}
					flagToRelayoutGui=true;
					state.hierHasChanged=true;
				}
					
			//check if it's any of the order controls
			for (int j=0;j<state.orderFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (orderRectangles[j][i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
						state.orderFields[j][i-1]=state.allowedOrderFields.get(index);
						flagToRelayoutGui=true;
						state.orderHasChanged=true;
					}

			//check if it's any of the size controls
			for (int j=0;j<state.sizeFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (sizeRectangles[j][i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
						state.sizeFields[j][i-1]=state.allowedSizeFields.get(index);
						flagToRelayoutGui=true;
						state.sizeHasChanged=true;
					}			

			//check if it's any of the colour controls
			for (int j=0;j<state.colourFields.length;j++)
				for (int i=1;i<state.hierarchyFields.length+1;i++) //use hierarchyFields.length, because it's per level
					if (colourRectangles[j][i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
						state.colourFields[j][i-1]=state.allowedColourFields.get(index);
						flagToRelayoutGui=true;
						state.colourHasChanged=true;
					}		
			
			//check if it's any of the layout controls
			for (int i=1;i<state.layouts.length+1;i++)
				if (layoutRectangles[i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
					state.layouts[i-1]=state.allowedLayouts.get(index);
					flagToRelayoutGui=true;
					state.layoutHasChanged=true;
				}

			//check if it's any of the appearance controls
			for (int i=1;i<state.appearanceValues.length+1;i++)
				if (appearanceRectangles[i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
					appearanceTypesEditing[i-1]=AppearanceType.values()[index];
					flagToRelayoutGui=true;
					state.appearanceHasChanged=true;
				}

			//check if it's any of the filters
			for (int i=1;i<state.filterValues.length+1;i++)
				if (filterRectangles[i].contains(mouseXOnMenuOpen,mouseYOnMenuOpen)){
					if (index==0)
						state.filterValues[i-1]=null;
					else
						state.filterValues[i-1]=state.hierarchyFields[i-1].getOrderValues().get(index-1);
					flagToRelayoutGui=true;
					state.hierHasChanged=true;
				}

			
		}
		showMenu=false;
	}
	
	/**Apply operator
	 * 
	 */
	public boolean applyOperator(Expression expression) throws ExpressionNotSupportedException {
		boolean result=state.applyOperator(expression);
		//relayout rectangles
		flagToRelayoutGui=true;
		return result;
	}
	
	/** Insert a level
	 * 
	 * @param level The level at which to insert
	 * @param hierarchyField The variable to insert
	 * @param orderField The order
	 * @param sizeField The size
	 * @param colourField The colour
	 * @param layout The layout
	 */
	public void insert(int level,DataField hierarchyField,SummariseField[] orderField,SummariseField[] sizeField, SummariseField[] colourField, Layout layout){
		state.insert(level, hierarchyField, orderField, sizeField, colourField, layout);
		flagToRelayoutGui=true;

		//Shift the information about which appearance state is being edited one level after where the new level has been inserted
		AppearanceType[] appearanceTypeEditing = new AppearanceType[state.getNumLevels()];
		for (int i=0;i<appearanceTypeEditing.length;i++){
			if (i<level){
				appearanceTypeEditing[i]=this.appearanceTypesEditing[i];
			}
			else if (i==level){
				if (level!=0){
					appearanceTypeEditing[i]=this.appearanceTypesEditing[i-1];
				}
				else{
					appearanceTypeEditing[i]=AppearanceType.SHOW_LABELS;
				}
			}
			else{
				appearanceTypeEditing[i]=this.appearanceTypesEditing[i-1];
			}
		}
		this.appearanceTypesEditing=appearanceTypeEditing;
	}

	/** Cut a level
	 * @param level The level that needs to be cut
	 */
	public void cut(int level){
		state.cut(level);
		flagToRelayoutGui=true;

		//Remove the information about which appearance state is being edited
		AppearanceType[] appearanceTypeEditing = new AppearanceType[state.getNumLevels()];
		for (int i=0;i<appearanceTypeEditing.length;i++){
			if (i<level){
				appearanceTypeEditing[i]=this.appearanceTypesEditing[i];
			}
			else{
				appearanceTypeEditing[i]=this.appearanceTypesEditing[i+1];
			}
		}
		this.appearanceTypesEditing=appearanceTypeEditing;
	}
	
	public void setState(TreemapState newState,boolean suppressHistoryAdd){
		TreemapState origState=this.state;
		
		//reset newState's changed flags
		newState.hierHasChanged=false;
		newState.orderHasChanged=false;
		newState.sizeHasChanged=false;
		newState.colourHasChanged=false;
		newState.layoutHasChanged=false;
		newState.appearanceHasChanged=false;

		//check is hier has changed
		if (origState.getNumLevels()!=newState.getNumLevels())
			newState.hierHasChanged=true;
		else{
			for (int i=0;i<origState.getNumLevels();i++){
				if (origState.hierarchyFields[i]!=newState.hierarchyFields[i]){
					newState.hierHasChanged=true;
				}
			}
		}

		//check order has changed
		if (origState.orderFields.length!=newState.orderFields.length)
			newState.orderHasChanged=true;
		else{
			for (int i=0;i<origState.orderFields.length;i++){
				if (origState.orderFields[i].length!=newState.orderFields[i].length)
					newState.orderHasChanged=true;
				else{
					for (int j=0;j<origState.orderFields[i].length;j++){
						if (origState.orderFields[i][j]!=newState.orderFields[i][j]){
							newState.orderHasChanged=true;
						}
					}
				}
			}
		}

		//check size has changed
		if (origState.sizeFields.length!=newState.sizeFields.length)
			newState.sizeHasChanged=true;
		else{
			for (int i=0;i<origState.sizeFields.length;i++){
				if (origState.sizeFields[i].length!=newState.sizeFields[i].length)
					newState.sizeHasChanged=true;
				else{
					for (int j=0;j<origState.sizeFields[i].length;j++){
						if (origState.sizeFields[i][j]!=newState.sizeFields[i][j]){
							newState.sizeHasChanged=true;
						}
					}
				}
			}
		}
		
		//check colour has changed
		if (origState.colourFields.length!=newState.colourFields.length)
			newState.colourHasChanged=true;
		else{
			for (int i=0;i<origState.colourFields.length;i++){
				if (origState.colourFields[i].length!=newState.colourFields[i].length)
					newState.colourHasChanged=true;
				else{
					for (int j=0;j<origState.colourFields[i].length;j++){
						if (origState.colourFields[i][j]!=newState.colourFields[i][j]){
							newState.colourHasChanged=true;
						}
					}
				}
			}
		}
		
		//check layout has changed
		if (origState.layouts.length!=newState.layouts.length)
			newState.layoutHasChanged=true;
		else{
			for (int i=0;i<origState.layouts.length;i++){
				if (origState.layouts[i]!=newState.layouts[i]){
					newState.layoutHasChanged=true;
				}
			}
		}

		//check appearance has changed
		if (origState.getNumLevels()!=newState.getNumLevels())
			newState.appearanceHasChanged=true;
		else{
			for (int i=0;i<origState.getNumLevels();i++){
				for (AppearanceType appearanceType:AppearanceType.values()){
					if (origState.getAppearanceValue(appearanceType, i)!=newState.getAppearanceValue(appearanceType, i)){
						newState.appearanceHasChanged=true;
					}
				}
			}
		}

		
		this.state=(TreemapState)newState.clone();
		this.state.suppressHistoryAdd=suppressHistoryAdd;
		
		if (this.appearanceTypesEditing.length<this.state.getNumLevels()){
			AppearanceType[] appearanceTypesEditing=new AppearanceType[state.getNumLevels()];
			for (int i=0;i<appearanceTypesEditing.length;i++){
				if (i<this.appearanceTypesEditing.length)
					appearanceTypesEditing[i]=this.appearanceTypesEditing[i];
				else
					appearanceTypesEditing[i]=AppearanceType.values()[0];
				this.appearanceTypesEditing=appearanceTypesEditing;
			}
		}
		
		flagToRelayoutGui=true;
	}
	
	public void clear(){
		state.clear();
		flagToRelayoutGui=true;	
	}

	/**Get the number of levels
	 * 
	 * @return Number of levels
	 */
	public int getNumLevels(){
		return state.hierarchyFields.length;
	}

	/**Gets the size variables
	 * 
	 * @return 2D array of the sizes (number of size dimension x number in hierarchy)
	 */
	public SummariseField[][] getSizeFields() {
		return state.sizeFields;
	}

	/**Gets the appearance values
	 * 
	 * @return array of appearance values (for each level)
	 */
	public double getAppearanceValue(AppearanceType appearanceType, int level){
		return state.appearanceValues[level].get(appearanceType);
	}
	
	/**Gets the variables that are allowed to be in the hierarchy
	 * 
	 * @return
	 */
	public List<DataField> getAllowedHierarchyFields() {
		return state.allowedHierarchyFields;
	}

	/**Gets the variables that are allowed to be used for size
	 * 
	 * @return
	 */
	public List<SummariseField> getAllowedSizeFields() {
		return state.allowedSizeFields;
	}

	/**Gets the variables that are allowed to be used for order
	 * 
	 * @return
	 */
	public List<SummariseField> getAllowedOrderFields() {
		return state.allowedOrderFields;
	}

	/**Gets the variables that are allowed to be used for colour
	 * 
	 * @return
	 */
	public List<SummariseField> getAllowedColourFields() {
		return state.allowedColourFields;
	}

	/**Gets the layouts that are offered
	 * 
	 * @return
	 */
	public List<Layout> getAllowedLayouts() {
		return state.allowedLayouts;
	}

	/**Get the variables in the hierarchy
	 * 
	 * @return Array of data variables, for each level 
	 */
	public DataField[] getHierarchyFields() {
		return state.hierarchyFields;
	}

	
	/**Get the filter values
	 * 
	 * @return Array of data variables, for each level 
	 */
	public Object[] getFilterValues() {
		return state.filterValues;
	}

	
	/**Get the order variables in the hierarchy
	 * 
	 * @return Array of variables (number of order dimensions X number in hierarchy) 
	 */
	public SummariseField[][] getOrderFields() {
		return state.orderFields;
	}
	
	/**Get the colour variables in the hierarchy
	 * 
	 * @return Array of variables (number of colour dimensions X number in hierarchy) 
	 */
	public SummariseField[][] getColourFields() {
		return state.colourFields;
	}

	/**Get the layouts in the hierarchy
	 * 
	 * @return Array of variables (number of order dimensions X number in hierarchy) 
	 */
	public Layout[] getLayouts() {
		return state.layouts;
	}


	/**Test is the hierarchy is empty
	 * 
	 * @return
	 */
	public boolean isEmpty(){
		return state.hierarchyFields.length==0;
	}

	
	 /**Reports whether the state has changed since last reset
	  * 
	  * @return
	  */
	 public boolean hasChanged(){
		 return state.hierHasChanged||state.orderHasChanged||state.sizeHasChanged||state.colourHasChanged||state.layoutHasChanged||state.appearanceHasChanged;
	 }

	 /**Reports whether the hierarchy has changed since last reset
	  * 
	  * @return
	  */	 
	 public boolean hierHasChanged(){
		 return state.hierHasChanged;
	 }
	 
	 /**Reports whether the order has changed since last reset
	  * 
	  * @return
	  */
	 public boolean orderHasChanged(){
		 return state.orderHasChanged;
	 }
	 
	 /**Reports whether the size has changed since last reset
	  * 
	  * @return
	  */
	 public boolean sizeHasChanged(){
		 return state.sizeHasChanged;
	 }
	 
	 /**Reports whether the colour has changed since last reset
	  * 
	  * @return
	  */
	 public boolean colourHasChanged(){
		 return state.colourHasChanged;
	 }
	 
	 /**Reports whether the layout has changed since last reset
	  * 
	  * @return
	  */
	 public boolean layoutHasChanged(){
		 return state.layoutHasChanged;
	 }
	 
	 /**Reports whether the appearance has changed since last reset
	  * 
	  * @return
	  */
	 public boolean appearanceHasChanged(){
		 return state.appearanceHasChanged;
	 }
	 
	 public void setAppearanceValue(int level,AppearanceType appearanceType,double value){
		 state.appearanceValues[level].put(appearanceType, value);
		 flagToRelayoutGui=true;
	 }

	 /**Restores a state from a state file
		 * If it finds HIVE\t[hive statement], it will restore that. Put it first though!
		 * The hierarchy length will change to the max HIER level - if not HIER is present, the hierarchy depth will not be affected
		 * 
		 * @param filename
		 */
	 public void inputStateFromFile(BufferedReader br) throws IOException,NumberFormatException{
		 state.inputStateFromFile(br);
		 flagToRelayoutGui=true;
	 }
	 
	 /**Restores a state from the contents of a state file
		 * If it finds HIVE\t[hive statement], it will restore that. Put it first though!
		 * The hierarchy length will change to the max HIER level - if not HIER is present, the hierarchy depth will not be affected
		 * 
		 * @param filename
		 */	 
	 public void inputStateFromStatefileContents(String stateFileContents) throws NumberFormatException{
		 state.inputStateFromStatefileContents(stateFileContents);
		 flagToRelayoutGui=true;
	 }

	 /**Clones the treemapState
	  * 
	  * @return
	  */
	 public TreemapState cloneState(){
		 return (TreemapState)state.clone();
	 }
	 
	 /**Return a saved state
	  * 
	  * @return
	  */
	 public SavedState getSavedState(){
		 return new SavedState(state);
	 }
	 
	public Collection<Expression> getState() {
		return state.getState();
	}


	public void saveStateToFile(String filename, boolean saveAppearanceOnly) throws IOException{
		state.saveStateToFile(filename,saveAppearanceOnly);
	}
	
	public String getSavedStateTextRepresention(boolean saveAppearanceOnly) {
		return state.getSavedStateTextRepresention(saveAppearanceOnly);
	}

	 /**Reset change flag. Call whether you've read these
	  * 
	  */
	 public void resetChangeFlags(){
		 this.state.resetChangeFlags();
	 }
	

	 public boolean stateEqualTo(TreemapState state){
		 return this.state.equals(state);
	 }
}